<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>2048</title>
    <style>
      body {
        font-size: 1rem;
      }
      div {
        box-sizing: border-box;
        border-radius: 5px;
      }
      .container {
        position: fixed;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        background: #ffeeaa;
      }
      .title {
        width: 100%;
        padding: 1em 0;
        font-size: 3em;
        color: #333;
        text-align: center;
      }
      .score {
        width: 100%;
        padding: 1em 0;
        font-size: 2em;
        color: #ff4433;
        text-align: center;
      }
      .item {
        display: flex;
        align-items: center;
        justify-content: center;
        color: #000;
        font-size: 2em;
      }
      .restart {
        position: absolute;
        right: 1em;
        top: 5em;
        padding: 5px 10px;
        font-size: 0.9rem;
        color: #666;
        border: 1px solid #00ffff;
        border-radius: 5px;
        cursor: pointer;
      }
    </style>
  </head>
  <body ondragstart="return false" onselectstart="return false">
    <div class="container">
      <div class="title">2048</div>
      <div class="score">
        0分
      </div>
      <div class="restart">重新开始</div>
    </div>

    <script>
      var bgColors = []

      bgColors[0] = 'rgb(198, 184, 172)'
      bgColors[2] = 'rgb(235, 224, 213)'
      bgColors[4] = 'rgb(234, 219, 193)'
      bgColors[8] = 'rgb(240, 167, 110)'
      bgColors[16] = 'rgb(244, 138, 89)'
      bgColors[32] = 'rgb(245, 112, 85)'
      bgColors[64] = 'rgb(245, 83, 52)'
      bgColors[128] = 'rgb(234, 200, 103)'
      bgColors[256] = 'rgb(234, 197, 87)'
      bgColors[512] = 'rgb(234, 192, 71)'
      bgColors[1024] = 'rgb(146, 208, 88)'
      bgColors[2048] = 'rgb(0, 176, 240)'

      window.onload = function() {
        // 数字数组
        const _ARR = [2, 4]
        // 盒子位置存储 4 * 4 16 二维数组 4列 4行
        const _ROW = 4
        // 块之间的间距 px
        const _MARGIN = 4
        // 滑动多少距离触发
        const LEN = 10
        // 存储所有块的位置
        const positions = []
        // 生成数字块 对应位置的值 0 即是没有
        let locationVal = []
        // 生成块的对象
        let blocks = []
        let score = 0

        const container = document.querySelector('.container')
        const oScore = document.querySelector('.score')
        const oRestart = document.querySelector('.restart')
        // 屏幕宽度
        const windowWidth = window.innerWidth

        // 游戏区域宽度
        const gameWidth = windowWidth > 375 ? 375 : windowWidth
        // 两边间距
        const twoMargin = (windowWidth - gameWidth) / 2

        // 块的宽度
        const itemWidth = (gameWidth - 5 * _MARGIN) / 4

        // 添加背景块
        for (let i = 0; i < _ROW; i++) {
          positions.push([0, 0, 0, 0])
          blocks.push([null, null, null, null])
          locationVal.push([0, 0, 0, 0])
          for (let j = 0; j < _ROW; j++) {
            // 背景块
            const bgItem = document.createElement('div')
            const left = twoMargin + _MARGIN * (j + 1) + itemWidth * j
            const bottom = (i + 1) * _MARGIN + i * itemWidth
            positions[i][j] = { left: left, bottom: bottom }
            bgItem.className = 'item'
            bgItem.style =
              'position: absolute; bottom: ' +
              bottom +
              'px;left: ' +
              left +
              'px;width:' +
              itemWidth +
              'px;height: ' +
              itemWidth +
              'px;background: ' +
              bgColors[0] +
              ';'
            container.appendChild(bgItem)
          }
        }

        // 初始化
        function init() {
          updateScore(0)

          if (blocks) {
            for (let i = 0; i < _ROW; i++) {
              for (let j = 0; j < _ROW; j++) {
                if (blocks[i][j] != null) {
                  container.removeChild(blocks[i][j])
                }
              }
            }
          }

          // 块的数字
          locationVal = []
          // 块的对象
          blocks = []
          for (let i = 0; i < _ROW; i++) {
            blocks.push([null, null, null, null])
            locationVal.push([0, 0, 0, 0])
          }

          addBlock()
          addBlock()
          addBlock()
        }
        // 找出空闲的位置
        function getEmptyLocations() {
          let locations = []
          for (let i = 0; i < _ROW; i++) {
            for (let j = 0; j < _ROW; j++) {
              if (locationVal[i][j] === 0) {
                locations.push({ x: i, y: j })
              }
            }
          }
          return locations
        }

        // 添加一个块

        function addBlock() {
          let locations = getEmptyLocations()
          if (locations.length === 0) {
            return false
          }
          let location = locations[Math.floor(Math.random() * locations.length)]
          let x = location.x
          let y = location.y
          let left = positions[x][y].left
          let bottom = positions[x][y].bottom
          let number = _ARR[Math.floor(Math.random() * _ARR.length)]

          const item = document.createElement('div')
          item.innerText = number
          locationVal[x][y] = number
          blocks[x][y] = item

          item.className = 'item'
          item.style =
            'position: absolute; bottom: ' +
            bottom +
            'px;left: ' +
            left +
            'px;width:' +
            itemWidth +
            'px;height: ' +
            itemWidth +
            'px;background: ' +
            bgColors[number] +
            ';'

          container.appendChild(item)
        }

        init()
        oRestart.addEventListener('click', init)
        // 移动块
        // @param {Object} block 移动块的节点对象
        // @param {Object} position 移动到的位置对象
        // @param {Function} callback 回调函数
        function doMove(block, position, callback) {
          block.style.bottom = position.bottom + 'px'
          block.style.left = position.left + 'px'

          // 移动完成后
          callback && callback()
        }
        function checkFail() {
          for (let i = 0; i < _ROW; i++) {
            for (let j = 0; j < _ROW; j++) {
              let n = locationVal[i][j]
              if (n == 0) return false
              // 可以作左移合并
              if (j > 0 && locationVal[i][j - 1] == n) return false
              // 可以右移合并
              if (j < _ROW - 1 && locationVal[i][j + 1] == n) return false
              // 可以下移合并
              if (i > 0 && locationVal[i - 1][j] == n) return false
              // 可以上移合并
              if (i < _ROW - 1 && locationVal[i + 1][j] == n) return false
            }
          }
          return true
        }
        function updateScore(number) {
          oScore.innerText = number + '分'
        }
        // 移动结束后
        function afterMove(hasMoved) {
          console.log(hasMoved)
          if (hasMoved) {
            updateScore(++score)
            addBlock()
          }
          if (checkFail()) {
            setTimeout(function() {
              let r = window.confirm('GameOver')
              if (r) {
                init()
              }
            }, 1000)
          }
        }
        // 根据方向移动
        function moveDirection(direction) {
          let hasMoved = false
          let move = function(x, y, callback) {
            let first, nextPosition, xy
            switch (direction) {
              case 'moveRight':
                first = y == _ROW - 1
                nextPosition = { x, y: y + 1 }
                xy = y
                break
              case 'moveLeft':
                first = y == 0
                nextPosition = { x, y: y - 1 }
                xy = y
                break
              case 'moveUp':
                first = x == _ROW - 1
                nextPosition = { x: x + 1, y }
                xy = x
                break
              case 'moveDown':
                first = x == 0
                nextPosition = { x: x - 1, y }
                xy = x
                break
              default:
                break
            }
            let nx = nextPosition.x
            let ny = nextPosition.y
            if (first || locationVal[x][y] == 0) {
              callback && callback()
              return
            } else if (locationVal[nx][ny] == locationVal[x][y]) {
              // 合并
              let block = blocks[x][y]
              let position = positions[x][y]
              // 与下一个数字相等 合并
              locationVal[nx][ny] += locationVal[x][y]
              locationVal[x][y] = 0

              blocks[x][y] = null
              blocks[nx][ny].innerText = locationVal[nx][ny]
              blocks[nx][ny].style.background = bgColors[locationVal[nx][ny]]
              doMove(block, position, () => {
                // 删除一个节点
                container.removeChild(block)
                callback && callback()
              })
              hasMoved = true
            } else if (locationVal[nx][ny] == 0) {
              // 移动
              let block = blocks[x][y]
              let position = positions[nx][ny]

              locationVal[nx][ny] = locationVal[x][y]
              locationVal[x][y] = 0

              blocks[nx][ny] = blocks[x][y]
              blocks[x][y] = null
              doMove(block, position, () => {
                if (xy == x) {
                  move(nx, y, callback)
                } else {
                  move(x, ny, callback)
                }
              })
              hasMoved = true
            } else {
              callback && callback()
              return
            }
          }
          // 取出可移动的块并且移动
          // 取出移动的块
          const toMove = []
          for (let i = 0; i < _ROW; i++) {
            for (let j = 0; j < _ROW; j++) {
              if (locationVal[i][j] !== 0) {
                toMove.push({ x: i, y: j })
              }
            }
          }
          let counter = 0
          for (let i = 0; i < toMove.length; i++) {
            move(toMove[i].x, toMove[i].y, () => {
              counter++
              if (counter === toMove.length) {
                afterMove(hasMoved)
              }
            })
          }
        }
        // 事件监听 方向判断
        function judgeDirction() {
          let tapStart, tapEnd
          // 判断为移动设备还是pc
          if (
            /(iPhone|iOS|Android|Windows Phone|iPad|iPod|BlackBerry)/i.test(
              navigator.userAgent
            )
          ) {
            tapStart = 'touchstart'
            tapEnd = 'touchend'
          } else {
            tapStart = 'mousedown'
            tapEnd = 'mouseup'
          }

          // 开始位置 结束位置
          let start, end

          container.addEventListener(tapStart, function(e) {
            start = { x: e.pageX, y: e.pageY }
          })
          container.addEventListener(tapEnd, function(e) {
            end = { x: e.pageX, y: e.pageY }

            let x = end.x - start.x
            let y = end.y - start.y
            let moveDirec
            // 如x的绝对值大于y的绝对值，则为x方向的滑动， 否则为y方向的滑动
            if (Math.abs(x) > Math.abs(y)) {
              if (Math.abs(x) < 10) return
              moveDirec = x > 0 ? 'moveRight' : 'moveLeft'
            } else {
              if (Math.abs(y) < 10) return
              moveDirec = y > 0 ? 'moveDown' : 'moveUp'
            }
            moveDirection(moveDirec)
          })
        }
        judgeDirction()
      }
    </script>
  </body>
</html>
